In recent years we have seen notable developments in cloud computing together with microservices architecture. Businesses are leaving monolith applications and on-premise data centers behind to utilize new paradigms to produce their business value as quickly as possible.
Microservices architecture helps teams to focus on their business logic independent of other developments that occur in an organization. Public clouds have an essential role in this transformation because it is not possible to achieve continuous deployments without an automated infrastructure provisioning. Distributed and automated software architecture comes with some challenges. We will be investigating common communication problems and alternative solutions in a microservices architecture.
Communication between different services was not a big problem within monolith applications. But remote endpoints must be invoked across a network in a microservices architecture. As developers and administrators, we found out that network connections are not reliable at all. As a result of this, we developed some techniques such as retry mechanisms, circuit breakers, and service meshes. Even those components are not sufficient because we should think about questions like what happens when a network timeout occurs for an HTTP request? Will retry cause duplicate data in the target system?
Another problem was the amount or speed of data that should be transferred over the network. Most of the systems have an upper limit in terms of processing power. If the target system cannot handle more than 50 requests per second, the clients should be aware of this and throttle requests accordingly. Otherwise, that limitation might cause a snowball effect and could end up with downtime of the whole system.
As a solution, I have identified two recently rising technologies. Reactive programming and RSocket are becoming a de-facto standard for that kind of workload. Reactive programming is a paradigm that allows developers two code business logic on top of stream using functions. Please be aware that ‘stream’ does not fundamentally mean a data stream. It can be the clicks of a user in a web application or incoming HTTP requests to a microservice. Our business logic -in other words, behavior- that runs on top of a stream modifies the incoming stream and potentially connects it to another stream. An example would be a microservice that reads some data from a database and modifies incoming database stream then eventually connects the modified stream to an HTTP response stream. HTTP is not a suitable protocol in the microservices architecture, so we need a stream starting from the client and ending in the database with back-pressure (flow-control) capabilities.
Unexpected loads and spikes cannot crash applications thanks to complete flow control between microservices without using complicated retry mechanisms.
In the rest of the article, we will investigate how we can create an end to end reactive communication mechanism between two different microservices using the following technologies:
Spring Boot: A popular Java framework that provides building blocks to develop microservices. https://spring.io/projects/spring-boot
RSocket: A new communication protocol that provides logical streams on top of a physical connection and provides flow control capabilities. http://rsocket.io/
R2DBC: Reactive JDBC client for relational databases.https://r2dbc.io/
Project Reactor: Reactive programming library for Java
https://projectreactor.io/
Let’s investigate the steps to create a reactive server using RSocket;
We need an entity named Person and create a reactive repository.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Data @AllArgsConstructor @NoArgsConstructor @Table("person") public class Person { @Id private String id; private String firstName; private String lastName; } public interface PersonRepository extends ReactiveCrudRepository < Person, Long > { Flux < Person > findByName(String name); }
Create an RSocket implementation that uses the reactive repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class PersonRSocket extends AbstractRSocket {
private final PersonRepository repository;
public PersonRSocket(PersonRepository personRepository) {
this.repository = personRepository;
}
@Override
public Flux < Payload > requestStream(Payload payload) {
return repository
.findAll()
.map(DefaultPayload::create);
}
}
Create a service and initiate the RSocket server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class PersonService implements ApplicationListener < ApplicationReadyEvent > {
private final PersonRSocket personRSocket;
public PersonService(PersonRSocket personRSocket) {
this.personRSocket = personRSocket;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
SocketAcceptor sa = (connectionSetupPayload, rSocket) - > Mono.just(personRSocket);
RSocketFactory
.receive()
.acceptor(sa)
.transport(TcpServerTransport.create("localhost", 8787))
.start()
.onTerminateDetach()
.subscribe();
}
}
At the client-side, you need to create a RSocket client and request a stream. Please note that RSocket also supports the request-response model over a single channel, but our goal is creating an end to end reactive flow.
1
2
3
4
5
6
7
8
9
public Flux < Person > getAllPersons() {
return RSocketFactory
.connect()
.transport(TcpClientTransport.create("localhost", 8787))
.start()
.flatMapMany(socket - > socket.requestStream(DefaultPayload.create(new byte[0])))
.map(Payload::getDataUtf8);
}
You can also visit https://start.spring.io/ and initialize a project to try this approach. Please include RSocket and R2DBC as dependencies. You also need to run a database and configure the spring reactive data framework accordingly.
I hope this article gave you a clue about reactive programming and frameworks around it. Please also note that reactive programming is not a magical solution for all types of problems and use it when it is suitable to your use-case.